﻿/**
 * 最简单的基于FFmpeg的视频播放器2(SDL升级版)
 * Simplest FFmpeg Player 2(SDL Update)
 *
 * 程序整体参照了雷霄骅的播放器。
 * 只不过，自己改用了最新版本的VS2022；最新版本的ffmpeg和最新版本的SDL，使用x64 架构得到库。
 * 因此更符合现在的解码流程
 * 
 */

#include <stdio.h>
#include <algorithm>

extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
#include "SDL2/SDL.h"
};


//Refresh Event
#define SFM_REFRESH_EVENT  (SDL_USEREVENT + 1)

#define SFM_BREAK_EVENT  (SDL_USEREVENT + 2)

int thread_exit=0;
int thread_pause=0;

int sfp_refresh_thread(void *opaque){
	thread_exit=0;
	thread_pause=0;

	while (!thread_exit) {
		if(!thread_pause){
			SDL_Event event;
			event.type = SFM_REFRESH_EVENT;
			SDL_PushEvent(&event);
		}
		SDL_Delay(33);
	}
	thread_exit=0;
	thread_pause=0;
	//Break
	SDL_Event event;
	event.type = SFM_BREAK_EVENT;
	SDL_PushEvent(&event);

	return 0;
}


int main(int argc, char* argv[])
{

	AVFormatContext	*pFormatCtx;
	AVCodecParameters *pCodecParam;
	AVCodecContext *pCodecCtx;
	int ret, got_picture;

	//------------SDL----------------
	int screen_w,screen_h;
	SDL_Window *screen; 
	SDL_Renderer* sdlRenderer;
	SDL_Texture* sdlTexture;
	SDL_Rect sdlRect;
	SDL_Thread *video_tid;
	SDL_Event event;

	struct SwsContext *img_convert_ctx;

	//char filepath[]="bigbuckbunny_480x272.h265";
	char filepath[]="Titanic.ts";
	//char filepath[]="https://live.iflyer360.com/live/live.flv";


	avformat_network_init();
	pFormatCtx = avformat_alloc_context();

	AVDictionary* format_opts = NULL;
	av_dict_set(&format_opts, "fflags", "nobuffer", 0);
	av_dict_set(&format_opts, "flags", "direct", 0);
	if(avformat_open_input(&pFormatCtx,filepath,NULL,&format_opts)!=0){
		printf("Couldn't open input stream.\n");
		return -1;
	}
	if(avformat_find_stream_info(pFormatCtx,NULL) < 0){
		printf("Couldn't find stream information.\n");
		return -1;
	}

	//检查流媒体中是否带有视频流
	int videoindex =- 1;
	for (int i = 0; i < pFormatCtx->nb_streams; i++)
	{
		if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
			videoindex = i;
			break;
		}
	}
	if(videoindex==-1){
		printf("Didn't find a video stream.\n");
		return -1;
	}

	//打印流格式信息
	printf("---------------- File Information ---------------\n");
	av_dump_format(pFormatCtx, 0, filepath, 0);
	printf("-------------------------------------------------\n");

	//获取视频流中的编解码信息 AVCodecParameters，
	pCodecParam = pFormatCtx->streams[videoindex]->codecpar;

	//根据编解码信息，创建对应的解码器实例
	const AVCodec *pCodec = avcodec_find_decoder(pCodecParam->codec_id);
	if(pCodec==NULL){
		printf("Codec not found.\n");
		return -1;
	}
	else 
	{
		printf("get Codec name:%s\n", avcodec_get_name(pCodecParam->codec_id));
	}

	//创建解码器上下文，用于记录解码过程中的相关信息
	pCodecCtx = avcodec_alloc_context3(pCodec);
	if (!pCodecCtx) {
		printf("Could not allocate video codec context");
		return -1;
	}
	//解码器上下文初始化，根据流中的解码器信息，转移部分信息至AVCodecContext中
	if (avcodec_parameters_to_context(pCodecCtx, pCodecParam) < 0)
	{
		printf("Could not full pCodecCtx from pCodecparam.");
		return -1;
	}

	//解码器启动
	if(avcodec_open2(pCodecCtx, pCodec,NULL)<0){
		printf("Could not open codec.\n");
		return -1;
	}


	//存放解码前的流信息
	AVPacket* packet = av_packet_alloc(); 
	
	//存放解码后，比例缩放之前的YUV信息，这里不需要初始化，因为后面的avcodec_receive_frame方法会初始化内存
	AVFrame* pFrame = av_frame_alloc();

	//存放比例缩放之后的YUV信息，这里需要初始化内存
	AVFrame* pFrameYUV = av_frame_alloc();
	unsigned char* out_buffer = (unsigned char *)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P,  pCodecCtx->width, pCodecCtx->height,1));
	av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize,out_buffer,AV_PIX_FMT_YUV420P,pCodecCtx->width, pCodecCtx->height,1);
	
	screen_w = std::min(pCodecCtx->width, 960);
	screen_h = std::min(pCodecCtx->height, 540);

	//初始化转码器，改变画幅比例
	img_convert_ctx = sws_getContext(pCodecParam->width, pCodecParam->height,
		AV_PIX_FMT_YUV420P,
		pCodecParam->width, pCodecParam->height,
		AV_PIX_FMT_YUV420P, 
		SWS_BICUBIC,
		NULL, NULL, NULL);
	

	//------------SDL start------------
	if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {  
		printf( "Could not initialize SDL - %s\n", SDL_GetError()); 
		return -1;
	} 

	//SDL 2.0 Support for multiple windows
	screen = SDL_CreateWindow("Simplest ffmpeg player's Window",
		SDL_WINDOWPOS_UNDEFINED,
		SDL_WINDOWPOS_UNDEFINED,
		screen_w, screen_h, 
		SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);

	if(!screen) {  
		printf("SDL: could not create window - exiting:%s\n",SDL_GetError());  
		return -1;
	}
	sdlRenderer = SDL_CreateRenderer(screen, -1, 0);  
	//IYUV: Y + U + V  (3 planes)
	//YV12: Y + V + U  (3 planes)
	sdlTexture = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING,pCodecCtx->width,pCodecCtx->height);  

	sdlRect.x=0;
	sdlRect.y=0;
	sdlRect.w=screen_w;
	sdlRect.h=screen_h;

	
	video_tid = SDL_CreateThread(sfp_refresh_thread,NULL,NULL);
	//------------SDL End------------
	
	//Event Loop
	for (;;) {
		SDL_WaitEvent(&event);
		if(event.type==SFM_REFRESH_EVENT){
			while(1){
				if(av_read_frame(pFormatCtx, packet)<0)
					thread_exit=1;

				if(packet->stream_index==videoindex)
					break;
			}
			ret = avcodec_send_packet(pCodecCtx, packet);
			got_picture = true;
			if(ret < 0){
				printf("Decode Error.\n");
				got_picture = false;
				return -1;
			}
			ret = avcodec_receive_frame(pCodecCtx, pFrame);
			if(got_picture){
				sws_scale(img_convert_ctx, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);
				
				//SDL渲染开始-------
				SDL_UpdateTexture( sdlTexture, NULL, pFrameYUV->data[0], pFrameYUV->linesize[0] );  
				SDL_RenderClear( sdlRenderer );  
				SDL_RenderCopy( sdlRenderer, sdlTexture, NULL, &sdlRect);  //渲染指定大小的窗口
				//SDL_RenderCopy( sdlRenderer, sdlTexture, NULL, NULL);  
				SDL_RenderPresent( sdlRenderer );  
				//SDL渲染结束-------
			}
			
		}else if(event.type==SDL_KEYDOWN){
			//Pause
			if(event.key.keysym.sym==SDLK_SPACE) thread_pause = ~thread_pause;
			//stop
			if(event.key.keysym.sym == SDLK_ESCAPE) thread_exit = 1;
		}else if(event.type==SDL_QUIT)
		{
			thread_exit=1;
		}else if(event.type==SFM_BREAK_EVENT)
		{
			break;
		}

	}

	sws_freeContext(img_convert_ctx);

	SDL_Quit();

	av_frame_free(&pFrameYUV);
	av_frame_free(&pFrame);
	avcodec_close(pCodecCtx);
	avformat_close_input(&pFormatCtx);
	av_packet_free(&packet);

	return 0;
}

